import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;

/**
 * Handles the concurrency side of things above the nitty-gritty.
 */
public class ConcurrencyHandler
{
    /**
     * All requests come through here.
     *
     * This function just forwards the request to relevant handlers.
     * It does nothing if the msg fromthe client is badly formed.
     *
     * @param c  The client from which the message came.
     * @param msg  That which the client sent.
     */
    public void on_recieve (Client c, String raw_msg)
    {

        try
        {
            JSONArray msgs = new JSONArray(raw_msg);

			for (int i=0; i< msgs.length(); i++)
			{
				JSONObject msg = msgs.getJSONObject(i);

				String type = msg.getString("type");

				if (type.equals("diff"))
				{
					this.on_diff(c, msg.getJSONArray("diff"));
				}
				else if (type.equals("subscribe"))
				{
					this.on_subscribe(c, msg.getString("user_alias"),
							msg.getString("document_id"));
				}
				else if (type.equals("unsubscribe"))
				{
					this.on_timeout(c);
				}
				else if (type.equals("sync"))
				{
					this.on_sync(c, msg.getString("content")); 
				}
				else if (! type.equals("poll"))
				{
					System.out.println("Unrecognised type: " + msg.toString());
				}
			}
        }
        catch (JSONException e)
        {
            System.out.println("Malformed message: " + raw_msg);
        }
    }

    /**
     * Called when the client is no longer connected.
     *
     * This is necessary so that the user list messages can be updated.
     *
     * @param c  The disconnected client.
     */ 
    public void on_timeout (Client c)
    {
        try
        {
            Document d = c.document;
            c.unsubscribe();
            d.sendUserList();
        }
        catch (JSONException e)
        {
            System.out.println("Could not timeout user:" +e);
        }
    }

    /**
     * A client has subscribed to a document.
     *
     * First welcome the client to the document.
     * Set the document into sync mode and send the presync message to its
     * clients.
     * Send the updated user list to all clients.
     *
     * @param c  The client
     * @param user_alias
     * @param document_id
     * @throws JSONException
     */
    private void on_subscribe (final Client c, String user_alias, 
                                String document_id) throws JSONException{
        
        if (c.document != null)
        {
            c.document.sendUserList();
            return;
        }
        Document d = Document.get(document_id);

        synchronized (d)
        {
            c.subscribe(user_alias, d);
            d.syncMode = true;

            final JSONObject reply_to_client =  new JSONObject();
            reply_to_client.put("type", "welcome");
            reply_to_client.put("alias", user_alias);
            reply_to_client.put("id", c.id);

            final JSONObject reply_to_others = new JSONObject();
            reply_to_others.put("type", "presync");

            d.forEachClient( new ClientAction () {
                public void action(Client client) {
                    if (c == client)
                    {
                        c.send(reply_to_client);
                    }
                    else
                    {
                        client.send(reply_to_others);
                    }
                }
            } );

            d.sendUserList();
        }

    };

    /**
     * A client has made changes to the document.
     *
     * Add the client's id to the diff message, and pass it on to all in the
     * client's document.
     *
     * @param c  The client
     * @param diff  The list of blocks
     * @throws JSONException
     */
    private void on_diff (Client c, JSONArray diff) throws JSONException {
        JSONObject diff_msg = new JSONObject();
        diff_msg.put("type", "diff");
        diff_msg.put("diff", diff);
        diff_msg.put("from", c.id);
        c.document.sendEachClient(diff_msg);
    };

    /**
     * A client has responded to {"type":"presync"}.
     *
     * If the document is still in syncMode, forward this content to everyone.
     * Otherwise silently drop it on the floor (to avoid pointless traffic)
     * 
     * @param c  The Client
     * @param content
     * @throws JSONException
     */
    private void on_sync (Client c, String content) throws JSONException {
        
        Document d = c.document;

        synchronized (d) {
            if (d.syncMode)
            {
                d.syncMode = false;
                JSONObject sync_msg = new JSONObject();
                sync_msg.put("type", "sync");
                sync_msg.put("content", content);
                d.sendEachClient(sync_msg);
            }
        }
    };
}
